Skip to content

feat(web): add Mantine clear buttons to text inputs app-wide (#1333)#1448

Merged
cliffhall merged 3 commits into
v2/mainfrom
feat/1333-input-clear-buttons
Jun 12, 2026
Merged

feat(web): add Mantine clear buttons to text inputs app-wide (#1333)#1448
cliffhall merged 3 commits into
v2/mainfrom
feat/1333-input-clear-buttons

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Summary

Closes #1333. Adds a clear (×) affordance to every user-editable text input across the web client so a populated field can be reset in a single click instead of select-all + delete.

Key finding

The issue proposed setting clearable: true as an app-wide theme default. In Mantine v8 (8.3.17) that only works for the combobox-based components (Autocomplete, Select, …) — plain TextInput and Textarea do not expose a public clearable prop (only the internal __clearable). So:

  • Autocompleteclearable enabled by default via theme/Autocomplete.ts (fires onChange(""), which also collapses the dropdown). Covers the prompt-argument and resource-template autocomplete branches.
  • TextInput / Textarea → clear button added per-site via rightSection + CloseButton (aria-label="Clear", rightSectionPointerEvents="auto"), rendered only when the field has a value and wired to reset the controlled value to "". This matches the "Left and right sections" pattern the issue's own Mantine link demonstrates.

A generic aria-label="Clear" is used (rather than field-specific labels) to match Mantine's own clear button and to avoid colliding with the many regex-based getByLabelText queries in the test suite.

Surfaces covered

Sidebar search (prompts, tools, apps, tasks, history, resources, network, logs), prompt-argument & resource-template variable inputs, sampling response/model fields, server config & settings forms, roots table, schema form, experimental features panel, and the server.json import panel.

Read-only/display Textareas (OutputValidationModal, UrlElicitationErrorModal) are intentionally left untouched.

Tests

Added clear-behavior unit tests on representative inputs from each category (sidebar search, prompt argument, resource-template variable, sampling response) plus the config/settings forms to keep per-file coverage ≥ 90%.

  • npm run validate
  • npm run test:integration
  • npm run test:storybook

🤖 Generated with Claude Code

Add a clear (×) affordance to every user-editable text input across the
web client so a populated field can be reset in one click.

- Autocomplete: enable `clearable` by default via the theme
  (`theme/Autocomplete.ts`) — covers the prompt-argument and
  resource-template variable autocomplete branches.
- TextInput / Textarea: Mantine v8 does not expose a public `clearable`
  prop on these primitives (only the combobox-based components do), so
  add the clear button manually via `rightSection` + `CloseButton`
  (aria-label="Clear") shown only when the field has a value, wired to
  reset the field's controlled value to "".

Applied to sidebar search inputs (prompts, tools, apps, tasks, history,
resources, network, logs), prompt-argument and resource-template
variable inputs, the sampling response/model fields, server config &
settings forms, roots table, schema form, experimental features panel,
and the server.json import panel. Read-only/display Textareas
(OutputValidationModal, UrlElicitationErrorModal) are intentionally left
untouched.

Tests cover the clear behavior on representative inputs from each
category (sidebar search, prompt argument, resource-template variable,
sampling response, plus the config/settings forms).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Jun 9, 2026
@cliffhall cliffhall linked an issue Jun 10, 2026 that may be closed by this pull request
@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 2m 1s —— View job


Review: clear buttons for text inputs (#1448)

Read the full diff against origin/v2/main and the surrounding code. The approach is sound and the per-site value ? <CloseButton> : null pattern is the correct Mantine v8 workaround for TextInput/Textarea not exposing public clearable. rightSectionPointerEvents="auto" is correctly set (default is none, which would make the button unclickable). Tests are well-scoped. Below are findings, ordered by impact.

1. Heavy duplication — extract a shared helper (main issue)

The identical 8–10 line block is repeated ~24 times across 15 files:

rightSectionPointerEvents="auto"
rightSection={
  value ? (
    <CloseButton onClick={reset} />
  ) : null
}

This is most of the 789 added lines, and AGENTS.md is explicit: "NEVER use inline code; instead extract to functions … located in a shared location if immediately reusable." A tiny helper collapses each site to one line and makes the a11y/disabled fixes below a one-place change:

// src/components/elements/clearableInput.tsx
export function clearableProps(value: string, onClear: () => void, label = "Clear") {
  return {
    rightSectionPointerEvents: "auto" as const,
    rightSection: value ? <CloseButton onClick={onClear} /> : null,
  };
}

Usage: <TextInput value={searchText} onChange={…} {...clearableProps(searchText, () => onSearchChange(""))} />. Fix this →

2. Clear button stays active while the input is disabled

In ServerConfigModal.tsx the fields are disabled={submitting}, but the CloseButton in rightSection is unaffected by the input's disabled and rightSectionPointerEvents="auto" keeps it clickable — so a user can clear a field mid-submit while it's supposedly locked. The helper should forward disabled to CloseButton (<CloseButton disabled={disabled} …/>), or each disabled site should pass it through.

3. Focus is dropped after clearing (a11y/UX)

Because rightSection becomes null once the value is "", the CloseButton unmounts on click and focus falls back to <body> — keyboard/screen-reader users lose their place. Mantine's native clearable refocuses the input. Consider onClick={() => { onClear(); inputRef.current?.focus(); }} (centralizing this in the helper makes it free everywhere).

4. Repeated generic aria-label="Clear" on the same screen

ServerSettingsForm (KeyValueRows/RootRows) and ExperimentalFeaturesPanel render many Clear buttons per screen — a screen-reader user hears "Clear, button" N times with no way to tell them apart. The PR description says generic labels were chosen to avoid colliding with getByLabelText test queries; that trades real a11y for test convenience. Since the helper takes a label arg, field-specific labels ("Clear key", "Clear value", `Clear ${arg.name}`) cost nothing and tests can scope with within(row) instead.

5. Clear button vertically centered on tall Textareas

For multi-row textareas (ServerConfigModal Arguments/Environment minRows={3}, ImportServerJsonPanel minRows={8}, ExperimentalFeaturesPanel minRows={6}), the rightSection CloseButton sits vertically centered, floating in the middle of a tall box and possibly overlapping the scrollbar. Mantine recommends top-aligning it via rightSectionProps={{ style: { alignItems: "flex-start" } }} for textareas. Minor, but worth a look since it affects several panels.


None of these block functionality — #1 (dedupe) is the one I'd most encourage before merge since it shrinks the diff and turns #2#4 into single-site fixes. #2 is the only correctness nit.

Address review #2 on #1448: the clear CloseButton sat in `rightSection`
with `rightSectionPointerEvents="auto"`, so it stayed clickable even
while the field was `disabled={submitting}`, letting a user wipe a field
mid-submit. Forward `disabled={submitting}` to each clear button so it
locks together with its input.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Thanks for the thorough review! Responses to each point:

#2 — clear button clickable while disabled (correctness) — ✅ fixed in df17d89. Forwarded disabled={submitting} to all six CloseButtons in ServerConfigModal, so each clear button locks together with its input during submit. These were the only disabled inputs in the change set.

#1 — extract a shared helper — declined (deliberate). The per-site inline approach was an explicit, up-front decision by the maintainer when scoping this work: the shared-helper and wrapper-component options were both considered and the inline option was chosen, knowing it's more repetitive. Keeping it as-is to honor that decision rather than reverse it in review. (Happy to revisit as a separate refactor if the team prefers.)

#4 — generic aria-label="Clear" — keeping (deliberate trade-off). This isn't only for test convenience: field-specific labels reintroduce real collisions with the suite's regex getByLabelText(/Command/i)-style queries (e.g. "Clear command" matches /Command/i), which is exactly what broke when descriptive labels were first tried. It also mirrors Mantine's own clear button, which is aria-hidden with no field-specific name. Each button sits adjacent to its labeled field, so the context is available to AT.

#3 — focus restoration on clear — deferred. Agreed it's a nice touch, but doing it across ~24 inline sites needs a ref per input, which pulls against the inline approach above; and since there was no clear button before, this isn't a regression. Noting it as a potential follow-up.

#5 — clear button vertically centered on tall Textareas — deferred (minor). The convention-compliant fix needs theme/CSS work rather than the rightSectionProps={{ style }} inline-style the docs suggest (inline styles are banned by CLAUDE.md). Tracking as a minor follow-up given it's cosmetic.

npm run validate, test:integration, and test:storybook all pass.

@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 1m 35s —— View job


Re-review: clear buttons for text inputs (#1448)

  • Read latest diff against origin/v2/main (incl. new commit df17d89d)
  • Verify the disabled-forwarding fix in ServerConfigModal
  • Assess responses to prior review points

Re-reviewed df17d89d and re-read the full change set. Thanks for the detailed responses.

#2 (correctness) — fix confirmed ✅

All six CloseButtons in ServerConfigModal now carry disabled={submitting}, so each clear button locks together with its input mid-submit. I also checked the other panels for stray clickable-while-locked clear buttons: SamplingRequestPanel only disables its action buttons (disabled={busy}), not its Textarea/TextInput, and ImportServerJsonPanel/ExperimentalFeaturesPanel have no disabled text inputs — so ServerConfigModal was indeed the only affected site. Coverage is complete.

#1 / #3 / #4 / #5 — accept the maintainer's calls

Spot checks

  • theme/Autocomplete.tsclearable: true default with per-site clearable={false} opt-out documented; correct for the combobox components and the right fix for the prompt-arg / resource-template autocomplete branches.
  • The per-site value ? <CloseButton/> : null + rightSectionPointerEvents="auto" pattern is applied consistently across all ~24 sites (PromptArgumentsForm, RootsTable, ToolControls, etc.) and wires each clear to the controlled "" reset.

No remaining blocking issues from me — the one correctness nit is resolved and the deferred items are reasonable, well-justified follow-ups. (I can't formally approve, but this LGTM.)

The sampling modal pre-loads its draft with the default stub result, and
"Send Response" sends that draft — so on an untouched form "Auto-respond"
did exactly the same thing as "Send Response", just with a fresh copy of
the same defaults. Drop the redundant button and its `onAutoRespond` prop;
"Send Response" now covers both the unedited (stub) and edited cases.

The InlineSamplingRequest variant keeps its Auto-respond button — there it
is the quick path versus "Edit & Send" (which opens this editable modal),
not a duplicate of an adjacent send action.

Updated PendingClientRequestModal wiring, stories, and tests accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cliffhall

Copy link
Copy Markdown
Member Author

@claude review — new commit f46cb35c removes the redundant Auto-respond button from SamplingRequestPanel (kept on the inline variant). Please re-review the latest diff.

@claude

claude Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 2m 0s —— View job


Re-review: remove redundant Auto-respond from SamplingRequestPanel (f46cb35c)

  • Read latest diff against origin/v2/main
  • Review removal of redundant Auto-respond button
  • Verify wiring, stories, and tests updated consistently

The refactor is correct and complete. 👍

Rationale checks out

The reasoning in the commit message holds: SamplingModalBody initializes draftResult from createDefaultSamplingResult(), and onSend is once(() => onRespond(draftResult)). On an untouched form draftResult is the stub, so the old onAutoRespond = once(() => onRespond(createDefaultSamplingResult())) produced an identical response — just a fresh copy of the same defaults. Two adjacent buttons doing the same thing on a pristine form was genuine redundancy, and "Send Response" now covers both the unedited and edited cases. Good call.

Inline variant correctly preserved

InlineSamplingRequest keeps its onAutoRespond (InlineSamplingRequest.tsx:123), where it's the quick path vs. "Edit & Send" (which opens this modal) — not a duplicate of an adjacent action. I grepped the tree: every remaining onAutoRespond reference now belongs to InlineSamplingRequest and its stories/tests (incl. ToolsScreen.stories.tsx:307, which is on an InlineSamplingRequest, not the panel). No dangling props on the modal path.

Wiring / props / no dead code

  • SamplingRequestPanelProps.onAutoRespond removed from the interface, the destructure, and the JSX button.
  • Button import is still used (the "Send Response" button at SamplingRequestPanel.tsx:207), so no now-unused import.
  • The createDefaultSamplingResult doc comment was updated to match the new single-button semantics — nice attention to detail.
  • Stories (SamplingRequestPanel, ToolsScreen, PendingClientRequestModal) and the SamplingModalBody wiring all drop the prop consistently.

Tests updated coherently

  • SamplingRequestPanel.test.tsx: the combined-buttons test is renamed and now asserts only onSend/onReject.
  • PendingClientRequestModal.test.tsx: the stub-result test is renamed to "sends the default stub sampling result when the draft is untouched" — accurately describes the new behavior — and the busy/lock test now exercises "Send Response" being clicked twice. Both still assert the stub payload, so coverage of the default-send path is preserved.
  • App.test.tsx and the story play functions retarget the button name. The "spinner clears" integration assertion is unchanged in intent.

Verification

Ran the two directly-affected suites locally — 29 tests pass (SamplingRequestPanel, PendingClientRequestModal). App.test.tsx only hit an environmental Vite module-resolution error on a core/ SDK import (test-servers/build not present in this checkout), not an assertion failure; its sole change here is a button-label swap.

No blocking issues — this LGTM. (I can't formally approve, but it's a clean, well-scoped removal.)
feat/1333-input-clear-buttons

@cliffhall cliffhall merged commit 0890c75 into v2/main Jun 12, 2026
1 check passed
@cliffhall cliffhall deleted the feat/1333-input-clear-buttons branch June 12, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Mantine clear buttons to all text inputs application-wide

1 participant